Skip to content

Subsystem 2b: rendered live map in a per-server #map channel (ImageSharp base tile + grid + cargo/heli/chinook markers) - #15

Merged
HandyS11 merged 15 commits into
developfrom
feat/map-render
Jun 17, 2026
Merged

Subsystem 2b: rendered live map in a per-server #map channel (ImageSharp base tile + grid + cargo/heli/chinook markers)#15
HandyS11 merged 15 commits into
developfrom
feat/map-render

Conversation

@HandyS11

Copy link
Copy Markdown
Owner

Subsystem 2b — the first of two map-render slices. Ships the rendered live map image in a new per-server #map channel (the live-event layer landed in 2a/2a-ii). No /map slash command, no map config UI — those and richer layers are deferred to 2b-ii.

What this does

  • New per-server #map channel (ReadOnly, like #events) holding one bot-managed message: a rendered PNG of the server map.
  • Render pipeline (ImageSharp): decode the static base-map tile (GetMapAsync().JpgImage, cached per connection), draw an A–Z grid + the live cargo ship / patrol helicopter / chinook markers at their current positions, encode a 1024×1024 PNG.
  • Refresh is event-driven + throttled: re-render+repost (delete+repost — the only reliable way to swap an attachment) at most once per Map:MapRefreshInterval (default 45s) per server, coalescing marker bursts. Base-map cache cleared on disconnect.

Architecture

New RustPlusBot.Features.Map project (mirrors Features.Events):

  • WorldToPixel (pure coord mapper) · MapRenderer (ImageSharp → PNG) · MarkerGlyphs (keyed glyph registry) · MapLayerSet (explicit layer value object)
  • BaseMapCache (singleton, per-(guild,server) base image) · MapComposer (cache + IEventState markers → PNG)
  • DiscordMapChannelPoster (delete+repost shim) · MapHostedService (throttled marker loop + disconnect-clear loop) · MapRefreshThrottle
  • New IRustServerQuery.GetMapImageAsync seam (base JPEG bytes) · Workspace #map ChannelSpec + IMapChannelLocator · MapOptions (validated)

No entity/migration/new gateway intent. Markers ride the existing 2a Rust+ socket state.

Deferred to 2b-ii (recorded for the next slice)

ServerMapSettings entity + layer-toggle UI · monument icons (skip unknown tokens) · wandering trader (TravellingVendor) · oil-rig activation styling · real icon assets (this slice draws glyphs). Also noted: the base-map cache invalidates only on disconnect, so a mid-session wipe serves a stale tile until reconnect — a natural 2b-ii follow-up.

Notes

  • Marker icons are drawn glyphs (C/H/K) this slice — no third-party image assets bundled. The vendored LiberationSans-Regular.ttf (SIL OFL 1.1) is embedded for map text; see NOTICE.
  • Built subagent-driven (13 tasks, per-task TDD + spec/quality reviews + a final whole-branch review). Build 0/0 strict analyzers, full suite 347 green across 9 assemblies (Map.Tests 19), jb-clean, no EF drift.

🤖 Generated with Claude Code

HandyS11 and others added 14 commits June 17, 2026 23:27
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap DeletePriorBotMessagesAsync in its own try/catch to prevent delete failures (e.g. missing Manage Messages permission) from aborting the repost. OperationCanceledException is rethrown for clean shutdown. Add LogDeleteFailed warning logger and document RecentMessageScan constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ct-clear

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 17, 2026 22:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Subsystem 2b” map-render slice that provisions a per-server #map channel and renders a live map PNG (base tile + grid + cargo/heli/chinook markers) using ImageSharp, wiring it into the host via DI and a new map-image query seam.

Changes:

  • Introduces RustPlusBot.Features.Map with rendering/composition/posting + a hosted service that refreshes map posts on events (throttled).
  • Extends workspace provisioning/locating to support a per-server #map channel (spec, localization, locator + tests).
  • Adds IRustServerQuery.GetMapImageAsync / connection implementations and comprehensive unit tests for the new map feature.

Reviewed changes

Copilot reviewed 41 out of 42 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/RustPlusBot.Features.Workspace.Tests/Locating/MapChannelLocatorTests.cs Adds tests for locating provisioned #map channels with TTL cache behavior.
tests/RustPlusBot.Features.Map.Tests/RustPlusBot.Features.Map.Tests.csproj New test project for the map feature.
tests/RustPlusBot.Features.Map.Tests/WorldToPixelTests.cs Verifies world→pixel projection math.
tests/RustPlusBot.Features.Map.Tests/MarkerGlyphsTests.cs Tests glyph mapping for known/unknown marker kinds.
tests/RustPlusBot.Features.Map.Tests/MapRendererTests.cs Tests ImageSharp render output sizing and marker overlay differences.
tests/RustPlusBot.Features.Map.Tests/MapRegistrationTests.cs Verifies DI registrations/singleton lifetimes.
tests/RustPlusBot.Features.Map.Tests/MapRefreshThrottleTests.cs Tests per-server refresh throttling.
tests/RustPlusBot.Features.Map.Tests/MapLayerSetTests.cs Tests the default 2b layer set configuration.
tests/RustPlusBot.Features.Map.Tests/MapComposerTests.cs Tests composition behavior with/without base map bytes.
tests/RustPlusBot.Features.Map.Tests/BaseMapCacheTests.cs Tests base map caching and eviction.
tests/RustPlusBot.Features.Connections.Tests/MapImageQueryTests.cs Adds coverage for the new base-map image query path when connected/not connected.
tests/RustPlusBot.Features.Connections.Tests/Fakes/FakeRustSocketSource.cs Extends fake socket with GetMapImageAsync behavior.
src/RustPlusBot.Host/RustPlusBot.Host.csproj References the new Map feature project.
src/RustPlusBot.Host/Program.cs Registers MapOptions and AddMap() in the host.
src/RustPlusBot.Features.Workspace/WorkspaceServiceCollectionExtensions.cs Registers IMapChannelLocator.
src/RustPlusBot.Features.Workspace/WorkspaceKeys.cs Adds WorkspaceChannelKeys.ServerMap.
src/RustPlusBot.Features.Workspace/Specs/ServerWorkspaceSpecProvider.cs Provisions the per-server #map channel spec.
src/RustPlusBot.Features.Workspace/Locating/MapChannelLocator.cs Adds TTL-cached resolution of provisioned #map channel IDs.
src/RustPlusBot.Features.Workspace/Locating/IMapChannelLocator.cs New interface for resolving #map channel IDs.
src/RustPlusBot.Features.Workspace/Localization/LocalizationCatalog.cs Adds localization keys for the #map channel name.
src/RustPlusBot.Features.Map/RustPlusBot.Features.Map.csproj New Map feature project with ImageSharp + embedded font asset.
src/RustPlusBot.Features.Map/Rendering/WorldToPixel.cs Implements world→pixel projection helper.
src/RustPlusBot.Features.Map/Rendering/MarkerPlacement.cs Value object for projected marker placement.
src/RustPlusBot.Features.Map/Rendering/MapRenderer.cs ImageSharp renderer: base tile resize + grid + marker glyph drawing.
src/RustPlusBot.Features.Map/Rendering/MapLayerSet.cs Layer configuration record with a fixed 2b default.
src/RustPlusBot.Features.Map/Posting/IMapChannelPoster.cs Interface for posting/replacing the map message in Discord.
src/RustPlusBot.Features.Map/Posting/DiscordMapChannelPoster.cs Discord implementation using delete+repost for attachments.
src/RustPlusBot.Features.Map/MapServiceCollectionExtensions.cs DI registration for renderer/cache/composer/poster/hosted service.
src/RustPlusBot.Features.Map/MapOptions.cs Adds configurable refresh interval (default 45s).
src/RustPlusBot.Features.Map/Hosting/MapRefreshThrottle.cs Implements per-(guild,server) refresh gating.
src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs Hosted service that reacts to marker changes and disconnects.
src/RustPlusBot.Features.Map/Composing/MapComposer.cs Composes base map + markers into a rendered PNG.
src/RustPlusBot.Features.Map/Composing/BaseMapCache.cs Caches base JPEG per (guild,server) with disconnect eviction.
src/RustPlusBot.Features.Map/Assets/MarkerGlyphs.cs Defines glyph color/letter mapping for markers.
src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs Adds GetMapImageAsync forwarding to live connection.
src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs Implements socket GetMapImageAsync via Rust+ API GetMapAsync.
src/RustPlusBot.Features.Connections/Listening/IRustServerConnection.cs Adds GetMapImageAsync to the connection interface.
src/RustPlusBot.Abstractions/Connections/IRustServerQuery.cs Adds GetMapImageAsync seam.
RustPlusBot.slnx Adds new Map projects to the solution.
NOTICE Documents embedded font licensing and notes future icon sourcing.
Directory.Packages.props Adds ImageSharp and ImageSharp.Drawing package versions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/RustPlusBot.Features.Map/Rendering/WorldToPixel.cs Outdated
Comment thread src/RustPlusBot.Features.Map/Rendering/MapRenderer.cs Outdated
Comment thread src/RustPlusBot.Features.Map/Rendering/MapLayerSet.cs
Comment thread src/RustPlusBot.Features.Map/Composing/MapComposer.cs Outdated
Comment thread src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs
- Periodic refresh loop: repaint every connected server's #map each
  MapRefreshInterval so moving cargo/heli/chinook (stable ids → no
  MapMarkersChangedEvent) track, and post the first image on connect.
  Marker-change + connect + tick all pass through the existing throttle.
- MapComposer reads dimensions from the new IRustServerQuery.GetMapDimensionsAsync
  seam instead of from a marker, so the grid renders on low-activity/zero-marker
  servers.
- WorldToPixel + MapRenderer grid scale each axis from its own dimension
  (Width/Height) — correct-by-construction for non-square maps.
- MapLayerSet doc: Grid draws lines (labels are 2b-ii), not "lines and labels".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HandyS11
HandyS11 merged commit b6bf89c into develop Jun 17, 2026
3 checks passed
@HandyS11
HandyS11 deleted the feat/map-render branch June 17, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants